#include "hamster_thread_pool.h"

// -------------- Sync Thread Pool ---------------
using namespace hamster;

std::string ThreadPoolSync::name()
{
    return "SyncThreadPool";
};

ThreadPoolSync::ThreadPoolSync(int num_threads) : ThreadPoolBase(num_threads)
{
    this->num_threads = num_threads;
    this->stop = false;
    this->id = {0};

    for (int i = 0; i < num_threads; i++)
    {
        workers.emplace_back(
            [this, i]() -> void
            {
                std::function<void()> task;
                while (true)
                {
                    {
                        std::unique_lock<std::mutex> lock(this->queue_mutex);
                        this->condition.wait(lock, [this]() -> bool
                                             { return this->stop || this->task_queue.empty() == false; });
                        if (this->stop && this->task_queue.empty() == true)
                            return;
                        task = std::move(this->task_queue.front());
                        this->task_queue.pop();
                    }
                    task();
                    this->check_waiting_queue();
                }
            });
    }
}

ThreadPoolSync::~ThreadPoolSync()
{
    {
        std::unique_lock<std::mutex> lock(this->queue_mutex);
        this->stop = true;
    }

    this->condition.notify_all();

    for (auto &worker : workers)
    {
        worker.join();
    }
}

template <typename F, typename... Args>
auto ThreadPoolSync::enqueue(F &&f, Args &&...args)
    -> std::future<typename std::result_of<typename std::decay<F>::type(typename std::decay<Args>::type...)>::type>
{
    using return_type = typename std::result_of<typename std::decay<F>::type(typename std::decay<Args>::type...)>::type;

    auto task = std::make_shared<std::packaged_task<return_type(void)>>(
        std::bind(std::forward<F>(f), std::forward<Args>(args)...));

    std::future<void> res = task->get_future();

    {
        std::unique_lock<std::mutex> lock(this->queue_mutex);
        if (this->stop)
            throw std::runtime_error("Enqueue on stopped thread pool");

        task_queue.emplace(
            [task]() -> void
            {
                (*task)();
            });
    }

    this->condition.notify_one();

    return res;
}

void ThreadPoolSync::run(Runnable *runnable, int num_total_tasks)
{
    if (num_total_tasks <= this->num_threads)
    {
        std::vector<std::future<void>> futures;

        for (int i = 0; i < num_total_tasks; i++)
        {
            int start = i;
            int end = (i == num_total_tasks - 1) ? num_total_tasks : (i + 1);
            futures.push_back(
                enqueue(
                        [&runnable, start, end, num_total_tasks]
                    ))
        }
    }
}